<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
  <title></title>
  <link href="../Styles/stylesheet.css" rel="stylesheet" type="text/css" />
  
<style type="text/css">
@page { margin-bottom: 5.000000pt; margin-top: 5.000000pt; }
</style>
</head>

<body>
  <h2><span style="border-bottom:1px solid">Chapter_24</span></h2>

  <p>范了一些有用的不同的边框。同样，它也使用RTTI来找我们使用的边框名（剔除所有的路径信息），然后将边框名放到面板中间的JLable里：</p>

  <p>//: Borders.java</p>

  <p>// Different Swing borders</p>

  <p>package c13.swing;</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import javax.swing.*;</p>

  <p>import javax.swing.border.*;</p>

  <p>public class Borders extends JPanel {</p>

  <p>static JPanel showBorder(Border b) {</p>

  <p>JPanel jp = new JPanel();</p>

  <p>jp.setLayout(new BorderLayout());</p>

  <p>String nm = b.getClass().toString();</p>

  <p>nm = nm.substring(nm.lastIndexOf('.') + 1);</p>

  <p>jp.add(new JLabel(nm, JLabel.CENTER),</p>

  <p>BorderLayout.CENTER);</p>

  <p>jp.setBorder(b);</p>

  <p>return jp;</p>

  <p>}</p>

  <p>public Borders() {</p>

  <p>setLayout(new GridLayout(2,4));</p>

  <p>add(showBorder(new TitledBorder("Title")));</p>

  <p>add(showBorder(new EtchedBorder()));</p>

  <p>add(showBorder(new LineBorder(Color.blue)));</p>

  <p>add(showBorder(</p>

  <p>new MatteBorder(5,5,30,30,Color.green)));</p>

  <p>add(showBorder(</p>

  <p>new BevelBorder(BevelBorder.RAISED)));</p>

  <p>add(showBorder(</p>

  <p>new SoftBevelBorder(BevelBorder.LOWERED)));</p>

  <p>add(showBorder(new CompoundBorder(</p>

  <p>new EtchedBorder(),</p>

  <p>new LineBorder(Color.red))));</p>

  <p>}</p>

  <p>public static void main(String args[]) {</p>

  <p>Show.inFrame(new Borders(), 500, 300);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>这一节中大多数程序例子都使用TitledBorder，但我们可以注意到其余的边框也同样易于使用。能创建我们自己的边框并安放它们到按钮、标签等等内――任何来自JComponent的东西。</p>

  <p>13.19.6 按钮</p>

  <p>Swing增加了一些不同类型的按钮，并且它同样可以修改选择组件的结构：所有的按钮、复选框、单选钮，甚至从AbstractButton处继承的菜单项（这是因为菜单项一般被包含在其中，它可能会被改进命名为“AbstractChooser”或者相同的什么名字）。我们会注意使用菜单项的简便，下面的例子展示了不同类型的可用的按钮：</p>

  <p>//: Buttons.java</p>

  <p>// Various Swing buttons</p>

  <p>package c13.swing;</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import javax.swing.*;</p>

  <p>import javax.swing.plaf.basic.*;</p>

  <p>import javax.swing.border.*;</p>

  <p>public class Buttons extends JPanel {</p>

  <p>JButton jb = new JButton("JButton");</p>

  <p>BasicArrowButton</p>

  <p>up = new BasicArrowButton(</p>

  <p>BasicArrowButton.NORTH),</p>

  <p>down = new BasicArrowButton(</p>

  <p>BasicArrowButton.SOUTH),</p>

  <p>right = new BasicArrowButton(</p>

  <p>BasicArrowButton.EAST),</p>

  <p>left = new BasicArrowButton(</p>

  <p>BasicArrowButton.WEST);</p>

  <p>public Buttons() {</p>

  <p>add(jb);</p>

  <p>add(new JToggleButton("JToggleButton"));</p>

  <p>add(new JCheckBox("JCheckBox"));</p>

  <p>add(new JRadioButton("JRadioButton"));</p>

  <p>JPanel jp = new JPanel();</p>

  <p>jp.setBorder(new TitledBorder("Directions"));</p>

  <p>jp.add(up);</p>

  <p>jp.add(down);</p>

  <p>jp.add(left);</p>

  <p>jp.add(right);</p>

  <p>add(jp);</p>

  <p>}</p>

  <p>public static void main(String args[]) {</p>

  <p>Show.inFrame(new Buttons(), 300, 200);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>JButton看起来像AWT按钮，但它没有更多可运行的功能（像我们后面将看到的如加入图像等）。在com.sun.java.swing.basic里，有一个更合适的BasicArrowButton按钮，但怎样测试它呢？有两种类型的“指针”恰好请求箭头按钮使用：Spinner修改一个中断值，并且StringSpinner通过一个字符串数组来移动（当它到达数组底部时，甚至会自动地封装）。ActionListeners附着在箭头按钮上展示它使用的这些相关指针：因为它们是Bean，我们将期待利用方法名，正好捕捉并设置它们的值。</p>

  <p>当我们运行这个程序例子时，我们会发现触发按钮保持它最新状态，开或时关。但复选框和单选钮每一个动作都相同，选中或没选中（它们从JToggleButton处继承）。</p>

  <p>13.19.7 按钮组</p>

  <p>如果我们想单选钮保持“异或”状态，我们必须增加它们到一个按钮组中，这几乎同老AWT中的方法相同但更加的灵活。在下面将要证明的程序例子是，一些AbstruactButton能被增加到一个ButtonGroup中。</p>

  <p>为避免重复一些代码，这个程序利用映射来生不同类型的按钮组。这会在makeBPanel中看到，makeBPanel创建了一个按钮组和一个JPanel，并且为数组中的每个String就是makeBPanel的第二个自变量增加一个类对象，由它的第一个自变量进行声明：</p>

  <p>//: ButtonGroups.java</p>

  <p>// Uses reflection to create groups of different</p>

  <p>// types of AbstractButton.</p>

  <p>package c13.swing;</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import javax.swing.*;</p>

  <p>import javax.swing.border.*;</p>

  <p>import java.lang.reflect.*;</p>

  <p>public class ButtonGroups extends JPanel {</p>

  <p>static String[] ids = {</p>

  <p>"June", "Ward", "Beaver",</p>

  <p>"Wally", "Eddie", "Lumpy",</p>

  <p>};</p>

  <p>static JPanel</p>

  <p>makeBPanel(Class bClass, String[] ids) {</p>

  <p>ButtonGroup bg = new ButtonGroup();</p>

  <p>JPanel jp = new JPanel();</p>

  <p>String title = bClass.getName();</p>

  <p>title = title.substring(</p>

  <p>title.lastIndexOf('.') + 1);</p>

  <p>jp.setBorder(new TitledBorder(title));</p>

  <p>for(int i = 0; i &lt; ids.length; i++) {</p>

  <p>AbstractButton ab = new JButton("failed");</p>

  <p>try {</p>

  <p>// Get the dynamic constructor method</p>

  <p>// that takes a String argument:</p>

  <p>Constructor ctor = bClass.getConstructor(</p>

  <p>new Class[] { String.class });</p>

  <p>// Create a new object:</p>

  <p>ab = (AbstractButton)ctor.newInstance(</p>

  <p>new Object[]{ids[i]});</p>

  <p>} catch(Exception ex) {</p>

  <p>System.out.println("can't create " +</p>

  <p>bClass);</p>

  <p>}</p>

  <p>bg.add(ab);</p>

  <p>jp.add(ab);</p>

  <p>}</p>

  <p>return jp;</p>

  <p>}</p>

  <p>public ButtonGroups() {</p>

  <p>add(makeBPanel(JButton.class, ids));</p>

  <p>add(makeBPanel(JToggleButton.class, ids));</p>

  <p>add(makeBPanel(JCheckBox.class, ids));</p>

  <p>add(makeBPanel(JRadioButton.class, ids));</p>

  <p>}</p>

  <p>public static void main(String args[]) {</p>

  <p>Show.inFrame(new ButtonGroups(), 500, 300);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>边框标题由类名剔除了所有的路径信息而来。AbstractButton初始化为一个JButton，JButtonr的标签发生“失效”，因此如果我们忽略这个异常信息，我们会在屏幕上一直看到这个问题。getConstructor()方法产生了一个通过getConstructor()方法安放自变量数组类型到类数组的构建器对象，然后所有我们要做的就是调用newInstance()，通过它一个数组对象包含我们当前的自变量――在这种例子中，就是ids数组中的字符串。</p>

  <p>这样增加了一些更复杂的内容到这个简单的程序中。为了使“异或”行为拥有按钮，我们创建一个按钮组并增加每个按钮到我们所需的组中。当我们运行这个程序时，我们会注意到所有的按钮除了JButton都会向我们展示“异或”行为。</p>

  <p>13.19.8 图标</p>

  <p>我们可在一个JLable或从AbstractButton处继承的任何事物中使用一个图标（包括JButton，JCheckbox，JradioButton及不同类型的JMenuItem)。利用JLables的图标十分的简单容易（我们会在随后的一个程序例子中看到）。下面的程序例子探索了我们可以利用按钮的图标和它们的衍生物的其它所有方法。</p>

  <p>我们可以使用任何我们需要的GIF文件，但在这个例子中使用的这个GIF文件是这本书编码发行的一部分，可以在www.BruceEckel.com处下载来使用。为了打开一个文件和随之带来的图像，简单地创建一个图标并分配它文件名。从那时起，我们可以在程序中使用这个产生的图标。</p>

  <p>//: Faces.java</p>

  <p>// Icon behavior in JButtons</p>

  <p>package c13.swing;</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import javax.swing.*;</p>

  <p>public class Faces extends JPanel {</p>

  <p>static Icon[] faces = {</p>

  <p>new ImageIcon("face0.gif"),</p>

  <p>new ImageIcon("face1.gif"),</p>

  <p>new ImageIcon("face2.gif"),</p>

  <p>new ImageIcon("face3.gif"),</p>

  <p>new ImageIcon("face4.gif"),</p>

  <p>};</p>

  <p>JButton</p>

  <p>jb = new JButton("JButton", faces[3]),</p>

  <p>jb2 = new JButton("Disable");</p>

  <p>boolean mad = false;</p>

  <p>public Faces() {</p>

  <p>jb.addActionListener(new ActionListener() {</p>

  <p>public void actionPerformed(ActionEvent e){</p>

  <p>if(mad) {</p>

  <p>jb.setIcon(faces[3]);</p>

  <p>mad = false;</p>

  <p>} else {</p>

  <p>jb.setIcon(faces[0]);</p>

  <p>mad = true;</p>

  <p>}</p>

  <p>jb.setVerticalAlignment(JButton.TOP);</p>

  <p>jb.setHorizontalAlignment(JButton.LEFT);</p>

  <p>}</p>

  <p>});</p>

  <p>jb.setRolloverEnabled(true);</p>

  <p>jb.setRolloverIcon(faces[1]);</p>

  <p>jb.setPressedIcon(faces[2]);</p>

  <p>jb.setDisabledIcon(faces[4]);</p>

  <p>jb.setToolTipText("Yow!");</p>

  <p>add(jb);</p>

  <p>jb2.addActionListener(new ActionListener() {</p>

  <p>public void actionPerformed(ActionEvent e){</p>

  <p>if(jb.isEnabled()) {</p>

  <p>jb.setEnabled(false);</p>

  <p>jb2.setText("Enable");</p>

  <p>} else {</p>

  <p>jb.setEnabled(true);</p>

  <p>jb2.setText("Disable");</p>

  <p>}</p>

  <p>}</p>

  <p>});</p>

  <p>add(jb2);</p>

  <p>}</p>

  <p>public static void main(String args[]) {</p>

  <p>Show.inFrame(new Faces(), 300, 200);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>一个图标可以在许多的构建器中使用，但我们可以使用setIcon()方法增加或更换图标。这个例子同样展示了当事件发生在JButton（或者一些AbstractButton）上时，为什么它可以设置各种各样的显示图标：当JButton被按下时，当它被失效时，或者“滚过”时（鼠标从它上面移动过但并不击它）。我们会注意到那给了按钮一种动画的感觉。</p>

  <p>注意工具提示条也同样增加到按钮中。</p>

  <p>13.19.9 菜单</p>

  <p>菜单在Swing中做了重要的改进并且更加的灵活――例如，我们可以在几乎程序中任何地方使用他们，包括在面板和程序片中。语法同它们在老的AWT中是一样的，并且这样使出现在老AWT的在新的Swing也出现了：我们必须为我们的菜单艰难地编写代码，并且有一些不再作为资源支持菜单（其它事件中的一些将使它们更易转换成其它的编程语言）。另外，菜单代码相当的冗长，有时还有一些混乱。下面的方法是放置所有的关于每个菜单的信息到对象的二维数组里（这种方法可以放置我们想处理的任何事物到数组里），这种方法在解决这个问题方面领先了一步。这个二维数组被菜单所创建，因此它首先表示出菜单名，并在剩余的列中表示菜单项和它们的特性。我们会注意到数组列不必保持一致――只要我们的代码知道将发生的一切事件，每一列都可以完全不同。</p>

  <p>//: Menus.java</p>

  <p>// A menu-building system; also demonstrates</p>

  <p>// icons in labels and menu items.</p>

  <p>package c13.swing;</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import javax.swing.*;</p>

  <p>public class Menus extends JPanel {</p>

  <p>static final Boolean</p>

  <p>bT = new Boolean(true),</p>

  <p>bF = new Boolean(false);</p>

  <p>// Dummy class to create type identifiers:</p>

  <p>static class MType { MType(int i) {} };</p>

  <p>static final MType</p>

  <p>mi = new MType(1), // Normal menu item</p>

  <p>cb = new MType(2), // Checkbox menu item</p>

  <p>rb = new MType(3); // Radio button menu item</p>

  <p>JTextField t = new JTextField(10);</p>

  <p>JLabel l = new JLabel("Icon Selected",</p>

  <p>Faces.faces[0], JLabel.CENTER);</p>

  <p>ActionListener a1 = new ActionListener() {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>t.setText(</p>

  <p>((JMenuItem)e.getSource()).getText());</p>

  <p>}</p>

  <p>};</p>

  <p>ActionListener a2 = new ActionListener() {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>JMenuItem mi = (JMenuItem)e.getSource();</p>

  <p>l.setText(mi.getText());</p>

  <p>l.setIcon(mi.getIcon());</p>

  <p>}</p>

  <p>};</p>

  <p>// Store menu data as "resources":</p>

  <p>public Object[][] fileMenu = {</p>

  <p>// Menu name and accelerator:</p>

  <p>{ "File", new Character('F') },</p>

  <p>// Name type accel listener enabled</p>

  <p>{ "New", mi, new Character('N'), a1, bT },</p>

  <p>{ "Open", mi, new Character('O'), a1, bT },</p>

  <p>{ "Save", mi, new Character('S'), a1, bF },</p>

  <p>{ "Save As", mi, new Character('A'), a1, bF},</p>

  <p>{ null }, // Separator</p>

  <p>{ "Exit", mi, new Character('x'), a1, bT },</p>

  <p>};</p>

  <p>public Object[][] editMenu = {</p>

  <p>// Menu name:</p>

  <p>{ "Edit", new Character('E') },</p>

  <p>// Name type accel listener enabled</p>

  <p>{ "Cut", mi, new Character('t'), a1, bT },</p>

  <p>{ "Copy", mi, new Character('C'), a1, bT },</p>

  <p>{ "Paste", mi, new Character('P'), a1, bT },</p>

  <p>{ null }, // Separator</p>

  <p>{ "Select All", mi,new Character('l'),a1,bT},</p>

  <p>};</p>

  <p>public Object[][] helpMenu = {</p>

  <p>// Menu name:</p>

  <p>{ "Help", new Character('H') },</p>

  <p>// Name type accel listener enabled</p>

  <p>{ "Index", mi, new Character('I'), a1, bT },</p>

  <p>{ "Using help", mi,new Character('U'),a1,bT},</p>

  <p>{ null }, // Separator</p>

  <p>{ "About", mi, new Character('t'), a1, bT },</p>

  <p>};</p>

  <p>public Object[][] optionMenu = {</p>

  <p>// Menu name:</p>

  <p>{ "Options", new Character('O') },</p>

  <p>// Name type accel listener enabled</p>

  <p>{ "Option 1", cb, new Character('1'), a1,bT},</p>

  <p>{ "Option 2", cb, new Character('2'), a1,bT},</p>

  <p>};</p>

  <p>public Object[][] faceMenu = {</p>

  <p>// Menu name:</p>

  <p>{ "Faces", new Character('a') },</p>

  <p>// Optinal last element is icon</p>

  <p>{ "Face 0", rb, new Character('0'), a2, bT,</p>

  <p>Faces.faces[0] },</p>

  <p>{ "Face 1", rb, new Character('1'), a2, bT,</p>

  <p>Faces.faces[1] },</p>

  <p>{ "Face 2", rb, new Character('2'), a2, bT,</p>

  <p>Faces.faces[2] },</p>

  <p>{ "Face 3", rb, new Character('3'), a2, bT,</p>

  <p>Faces.faces[3] },</p>

  <p>{ "Face 4", rb, new Character('4'), a2, bT,</p>

  <p>Faces.faces[4] },</p>

  <p>};</p>

  <p>public Object[] menuBar = {</p>

  <p>fileMenu, editMenu, faceMenu,</p>

  <p>optionMenu, helpMenu,</p>

  <p>};</p>

  <p>static public JMenuBar</p>

  <p>createMenuBar(Object[] menuBarData) {</p>

  <p>JMenuBar menuBar = new JMenuBar();</p>

  <p>for(int i = 0; i &lt; menuBarData.length; i++)</p>

  <p>menuBar.add(</p>

  <p>createMenu((Object[][])menuBarData[i]));</p>

  <p>return menuBar;</p>

  <p>}</p>

  <p>static ButtonGroup bgroup;</p>

  <p>static public JMenu</p>

  <p>createMenu(Object[][] menuData) {</p>

  <p>JMenu menu = new JMenu();</p>

  <p>menu.setText((String)menuData[0][0]);</p>

  <p>menu.setMnemonic(</p>

  <p>((Character)menuData[0][1]).charValue());</p>

  <p>// Create redundantly, in case there are</p>

  <p>// any radio buttons:</p>

  <p>bgroup = new ButtonGroup();</p>

  <p>for(int i = 1; i &lt; menuData.length; i++) {</p>

  <p>if(menuData[i][0] == null)</p>

  <p>menu.add(new JSeparator());</p>

  <p>else</p>

  <p>menu.add(createMenuItem(menuData[i]));</p>

  <p>}</p>

  <p>return menu;</p>

  <p>}</p>

  <p>static public JMenuItem</p>

  <p>createMenuItem(Object[] data) {</p>

  <p>JMenuItem m = null;</p>

  <p>MType type = (MType)data[1];</p>

  <p>if(type == mi)</p>

  <p>m = new JMenuItem();</p>

  <p>else if(type == cb)</p>

  <p>m = new JCheckBoxMenuItem();</p>

  <p>else if(type == rb) {</p>

  <p>m = new JRadioButtonMenuItem();</p>

  <p>bgroup.add(m);</p>

  <p>}</p>

  <p>m.setText((String)data[0]);</p>

  <p>m.setMnemonic(</p>

  <p>((Character)data[2]).charValue());</p>

  <p>m.addActionListener(</p>

  <p>(ActionListener)data[3]);</p>

  <p>m.setEnabled(</p>

  <p>((Boolean)data[4]).booleanValue());</p>

  <p>if(data.length == 6)</p>

  <p>m.setIcon((Icon)data[5]);</p>

  <p>return m;</p>

  <p>}</p>

  <p>Menus() {</p>

  <p>setLayout(new BorderLayout());</p>

  <p>add(createMenuBar(menuBar),</p>

  <p>BorderLayout.NORTH);</p>

  <p>JPanel p = new JPanel();</p>

  <p>p.setLayout(new BorderLayout());</p>

  <p>p.add(t, BorderLayout.NORTH);</p>

  <p>p.add(l, BorderLayout.CENTER);</p>

  <p>add(p, BorderLayout.CENTER);</p>

  <p>}</p>

  <p>public static void main(String args[]) {</p>

  <p>Show.inFrame(new Menus(), 300, 200);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>这个程序的目的是允许程序设计者简单地创建表格来描述每个菜单，而不是输入代码行来建立菜单。每个菜单都产生一个菜单，表格中的第一列包含菜单名和键盘快捷键。其余的列包含每个菜单项的数据：字符串存在在菜单项中的位置，菜单的类型，它的快捷键，当菜单项被选中时被激活的动作接收器及菜单是否被激活等信息。如果列开始处是空的，它将被作为一个分隔符来处理。</p>

  <p>为了预防浪费和冗长的多个Boolean创建的对象和类型标志，以下的这些在类开始时就作为static final被创建：bT和bF描述Booleans和哑类MType的不同对象描述标准的菜单项（mi），复选框菜单项（cb），和单选钮菜单项（rb）。请记住一组Object可以拥有单一的Object句柄，并且不再是原来的值。</p>

  <p>这个程序例子同样展示了JLables和JMenuItems（和它们的衍生事物）如何处理图标的。一个图标经由它的构建器置放进JLable中并当对应的菜单项被选中时被改变。</p>

  <p>菜单条数组控制处理所有在文件菜单清单中列出的，我们想显示在菜单条上的文件菜单。我们通过这个数组去使用createMenuBar()，将数组分类成单独的菜单数据数组，再通过每个单独的数组去创建菜单。这种方法依次使用菜单数据的每一行并以该数据创建JMenu，然后为菜单数据中剩下的每一行调用createMenuItem()方法。最后，createMenuItem()方法分析菜单数据的每一行并且判断菜单类型和它的属性，再适当地创建菜单项。终于，像我们在菜单构建器中看到的一样，从表示createMenuBar(menuBar)的表格中创建菜单，而所有的事物都是采用递归方法处理的。</p>

  <p>这个程序不能建立串联的菜单，但我们拥有足够的知识，如果我们需要的话，随时都能增加多级菜单进去。</p>

  <p>13.19.10 弹出式菜单</p>

  <p>JPopupMenu的执行看起来有一些别扭：我们必须调用enableEvents()方法并选择鼠标事件代替利用事件接收器。它可能增加一个鼠标接收器但MouseEvent从isPopupTrigger()处不会返回真值――它不知道将激活一个弹出菜单。另外，当我们尝试接收器方法时，它的行为令人不可思议，这或许是鼠标单击活动引起的。在下面的程序例子里一些事件产生了这种弹出行为：</p>

  <p>//: Popup.java</p>

  <p>// Creating popup menus with Swing</p>

  <p>package c13.swing;</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import javax.swing.*;</p>

  <p>public class Popup extends JPanel {</p>

  <p>JPopupMenu popup = new JPopupMenu();</p>

  <p>JTextField t = new JTextField(10);</p>

  <p>public Popup() {</p>

  <p>add(t);</p>

  <p>ActionListener al = new ActionListener() {</p>

  <p>public void actionPerformed(ActionEvent e){</p>

  <p>t.setText(</p>

  <p>((JMenuItem)e.getSource()).getText());</p>

  <p>}</p>

  <p>};</p>

  <p>JMenuItem m = new JMenuItem("Hither");</p>

  <p>m.addActionListener(al);</p>

  <p>popup.add(m);</p>

  <p>m = new JMenuItem("Yon");</p>

  <p>m.addActionListener(al);</p>

  <p>popup.add(m);</p>

  <p>m = new JMenuItem("Afar");</p>

  <p>m.addActionListener(al);</p>

  <p>popup.add(m);</p>

  <p>popup.addSeparator();</p>

  <p>m = new JMenuItem("Stay Here");</p>

  <p>m.addActionListener(al);</p>

  <p>popup.add(m);</p>

  <p>PopupListener pl = new PopupListener();</p>

  <p>addMouseListener(pl);</p>

  <p>t.addMouseListener(pl);</p>

  <p>}</p>

  <p>class PopupListener extends MouseAdapter {</p>

  <p>public void mousePressed(MouseEvent e) {</p>

  <p>maybeShowPopup(e);</p>

  <p>}</p>

  <p>public void mouseReleased(MouseEvent e) {</p>

  <p>maybeShowPopup(e);</p>

  <p>}</p>

  <p>private void maybeShowPopup(MouseEvent e) {</p>

  <p>if(e.isPopupTrigger()) {</p>

  <p>popup.show(</p>

  <p>e.getComponent(), e.getX(), e.getY());</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String args[]) {</p>

  <p>Show.inFrame(new Popup(),200,150);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>相同的ActionListener被加入每个JMenuItem中，使其能从菜单标签中取出文字，并将文字插入JTextField。</p>

  <p>13.19.11 列表框和组合框</p>

  <p>列表框和组合框在Swing中工作就像它们在老的AWT中工作一样，但如果我们需要它，它们同样被增加功能。另外，它也更加的方便易用。例如，JList中有一个显示String数组的构建器（奇怪的是同样的功能在JComboBox中无效！）。下面的例子显示了它们基本的用法。</p>

  <p>//: ListCombo.java</p>

  <p>// List boxes &amp; Combo boxes</p>

  <p>package c13.swing;</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import javax.swing.*;</p>

  <p>public class ListCombo extends JPanel {</p>

  <p>public ListCombo() {</p>

  <p>setLayout(new GridLayout(2,1));</p>

  <p>JList list = new JList(ButtonGroups.ids);</p>

  <p>add(new JScrollPane(list));</p>

  <p>JComboBox combo = new JComboBox();</p>

  <p>for(int i = 0; i &lt; 100; i++)</p>

  <p>combo.addItem(Integer.toString(i));</p>

  <p>add(combo);</p>

  <p>}</p>

  <p>public static void main(String args[]) {</p>

  <p>Show.inFrame(new ListCombo(),200,200);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>最开始的时候，似乎有点儿古怪的一种情况是JLists居然不能自动提供滚动特性――即使那也许正是我们一直所期望的。增加对滚动的支持变得十分容易，就像上面示范的一样――简单地将JList封装到JScrollPane即可，所有的细节都自动地为我们照料到了。</p>

  <p>13.19.12 滑杆和进度指示条</p>

  <p>滑杆用户能用一个滑块的来回移动来输入数据，在很多情况下显得很直观（如声音控制）。进程条从“空”到“满”显示相关数据的状态，因此用户得到了一个状态的透视。我最喜爱的有关这的程序例子简单地将滑动块同进程条挂接起来，所以当我们移动滑动块时，进程条也相应的改变：</p>

  <p>//: Progress.java</p>

  <p>// Using progress bars and sliders</p>

  <p>package c13.swing;</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import javax.swing.*;</p>

  <p>import javax.swing.event.*;</p>

  <p>import javax.swing.border.*;</p>

  <p>public class Progress extends JPanel {</p>

  <p>JProgressBar pb = new JProgressBar();</p>

  <p>JSlider sb =</p>

  <p>new JSlider(JSlider.HORIZONTAL, 0, 100, 60);</p>

  <p>public Progress() {</p>

  <p>setLayout(new GridLayout(2,1));</p>

  <p>add(pb);</p>

  <p>sb.setValue(0);</p>

  <p>sb.setPaintTicks(true);</p>

  <p>sb.setMajorTickSpacing(20);</p>

  <p>sb.setMinorTickSpacing(5);</p>

  <p>sb.setBorder(new TitledBorder("Slide Me"));</p>

  <p>pb.setModel(sb.getModel()); // Share model</p>

  <p>add(sb);</p>

  <p>}</p>

  <p>public static void main(String args[]) {</p>

  <p>Show.inFrame(new Progress(),200,150);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>JProgressBar十分简单，但JSlider却有许多选项，例如方法、大或小的记号标签。注意增加一个带标题的边框是多么的容易。</p>

  <p>13.19.13 树</p>

  <p>使用一个JTree可以简单地像下面这样表示：</p>

  <p>add(new JTree(</p>

  <p>new Object[] {"this", "that", "other"}));</p>

  <p>这个程序显示了一个原始的树状物。树状物的API是非常巨大的，可是――当然是在Swing中的巨大。它表明我们可以做有关树状物的任何事，但更复杂的任务可能需要不少的研究和试验。幸运的是，在库中提供了一个妥协：“默认的”树状物组件，通常那是我们所需要的。因此大多数的时间我们可以利用这些组件，并且只在特殊的情况下我们需要更深入的研究和理解。</p>

  <p>下面的例子使用了“默认”的树状物组件在一个程序片中显示一个树状物。当我们按下按钮时，一个新的子树就被增加到当前选中的结点下（如果没有结点被选中，就用根结节）：</p>

  <p>//: Trees.java</p>

  <p>// Simple Swing tree example. Trees can be made</p>

  <p>// vastly more complex than this.</p>

  <p>package c13.swing;</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import javax.swing.*;</p>

  <p>import javax.swing.tree.*;</p>

  <p>// Takes an array of Strings and makes the first</p>

  <p>// element a node and the rest leaves:</p>

  <p>class Branch {</p>

  <p>DefaultMutableTreeNode r;</p>

  <p>public Branch(String[] data) {</p>

  <p>r = new DefaultMutableTreeNode(data[0]);</p>

  <p>for(int i = 1; i &lt; data.length; i++)</p>

  <p>r.add(new DefaultMutableTreeNode(data[i]));</p>

  <p>}</p>

  <p>public DefaultMutableTreeNode node() {</p>

  <p>return r;</p>

  <p>}</p>

  <p>}</p>

  <p>public class Trees extends JPanel {</p>

  <p>String[][] data = {</p>

  <p>{ "Colors", "Red", "Blue", "Green" },</p>

  <p>{ "Flavors", "Tart", "Sweet", "Bland" },</p>

  <p>{ "Length", "Short", "Medium", "Long" },</p>

  <p>{ "Volume", "High", "Medium", "Low" },</p>

  <p>{ "Temperature", "High", "Medium", "Low" },</p>

  <p>{ "Intensity", "High", "Medium", "Low" },</p>

  <p>};</p>

  <p>static int i = 0;</p>

  <p>DefaultMutableTreeNode root, child, chosen;</p>

  <p>JTree tree;</p>

  <p>DefaultTreeModel model;</p>

  <p>public Trees() {</p>

  <p>setLayout(new BorderLayout());</p>

  <p>root = new DefaultMutableTreeNode("root");</p>

  <p>tree = new JTree(root);</p>

  <p>// Add it and make it take care of scrolling:</p>

  <p>add(new JScrollPane(tree),</p>

  <p>BorderLayout.CENTER);</p>

  <p>// Capture the tree's model:</p>

  <p>model =(DefaultTreeModel)tree.getModel();</p>

  <p>JButton test = new JButton("Press me");</p>

  <p>test.addActionListener(new ActionListener() {</p>

  <p>public void actionPerformed(ActionEvent e){</p>

  <p>if(i &lt; data.length) {</p>

  <p>child = new Branch(data[i++]).node();</p>

  <p>// What's the last one you clicked?</p>

  <p>chosen = (DefaultMutableTreeNode)</p>

  <p>tree.getLastSelectedPathComponent();</p>

  <p>if(chosen == null) chosen = root;</p>

  <p>// The model will create the</p>

  <p>// appropriate event. In response, the</p>

  <p>// tree will update itself:</p>

  <p>model.insertNodeInto(child, chosen, 0);</p>

  <p>// This puts the new node on the</p>

  <p>// currently chosen node.</p>

  <p>}</p>

  <p>}</p>

  <p>});</p>

  <p>// Change the button's colors:</p>

  <p>test.setBackground(Color.blue);</p>

  <p>test.setForeground(Color.white);</p>

  <p>JPanel p = new JPanel();</p>

  <p>p.add(test);</p>

  <p>add(p, BorderLayout.SOUTH);</p>

  <p>}</p>

  <p>public static void main(String args[]) {</p>

  <p>Show.inFrame(new Trees(),200,500);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>最重要的类就是分支，它是一个工具，用来获取一个字符串数组并为第一个字符串建立一个DefaultMutableTreeNode作为根，其余在数组中的字符串作为叶。然后node()方法被调用以产生“分支”的根。树状物类包括一个来自被制造的分支的二维字符串数组，以及用来统计数组的一个静态中断i。DefaultMutableTreeNode对象控制这个结节，但在屏幕上表示的是被JTree和它的相关（DefaultTreeModel）模式所控制。注意当JTree被增加到程序片时，它被封装到JScrollPane中――这就是它全部提供的自动滚动。</p>

  <p>JTree通过它自己的模型来控制。当我们修改这个模型时，模型产生一个事件，导致JTree对可以看见的树状物完成任何必要的升级。在init()中，模型由调用getModel()方法所捕捉。当按钮被按下时，一个新的分支被创建了。然后，当前选择的组件被找到（如果没有选择就是根）并且模型的insertNodeInto()方法做所有的改变树状物和导致它升级的工作。</p>

  <p>大多数的时候，就像上面的例子一样，程序将给我们在树状物中所需要的一切。不过，树状物拥有力量去做我们能够想像到的任何事――在上面的例子中我们到处都可看到“default（默认）”字样，我们可以取代我们自己的类来获取不同的动作。但请注意：几乎所有这些类都有一个具大的接口，因此我们可以花一些时间努力去理解这些错综复杂的树状物。</p>

  <p>13.19.14 表格</p>

  <p>和树状物一样，表格在Swing相当的庞大和强大。它们最初有意被设计成以Java数据库连结（JDBC，在15章有介绍）为媒介的“网格”数据库接口，并且因此它们拥有的巨大的灵活性，使我们不再感到复杂。无疑，这是足以成为成熟的电子数据表的基础条件而且可能为整本书提供很好的根据。但是，如果我们理解这个的基础条件，它同样可能创建相关的简单的Jtable。</p>

  <p>JTable控制数据的显示方式，但TableModel控制它自己的数据。因此在我们创建JTable前，应先创建一个TableModel。我们可以全部地执行TableModel接口，但它通常从helper类的AbstractTableModel处简单地继承：</p>

  <p>//: Table.java</p>

  <p>// Simple demonstration of JTable</p>

  <p>package c13.swing;</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import javax.swing.*;</p>

  <p>import javax.swing.table.*;</p>

  <p>import javax.swing.event.*;</p>

  <p>// The TableModel controls all the data:</p>

  <p>class DataModel extends AbstractTableModel {</p>

  <p>Object[][] data = {</p>

  <p>{"one", "two", "three", "four"},</p>

  <p>{"five", "six", "seven", "eight"},</p>

  <p>{"nine", "ten", "eleven", "twelve"},</p>

  <p>};</p>

  <p>// Prints data when table changes:</p>

  <p>class TML implements TableModelListener {</p>

  <p>public void tableChanged(TableModelEvent e) {</p>

  <p>for(int i = 0; i &lt; data.length; i++) {</p>

  <p>for(int j = 0; j &lt; data[0].length; j++)</p>

  <p>System.out.print(data[i][j] + " ");</p>

  <p>System.out.println();</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>DataModel() {</p>

  <p>addTableModelListener(new TML());</p>

  <p>}</p>

  <p>public int getColumnCount() {</p>

  <p>return data[0].length;</p>

  <p>}</p>

  <p>public int getRowCount() {</p>

  <p>return data.length;</p>

  <p>}</p>

  <p>public Object getValueAt(int row, int col) {</p>

  <p>return data[row][col];</p>

  <p>}</p>

  <p>public void</p>

  <p>setValueAt(Object val, int row, int col) {</p>

  <p>data[row][col] = val;</p>

  <p>// Indicate the change has happened:</p>

  <p>fireTableDataChanged();</p>

  <p>}</p>

  <p>public boolean</p>

  <p>isCellEditable(int row, int col) {</p>

  <p>return true;</p>

  <p>}</p>

  <p>};</p>

  <p>public class Table extends JPanel {</p>

  <p>public Table() {</p>

  <p>setLayout(new BorderLayout());</p>

  <p>JTable table = new JTable(new DataModel());</p>

  <p>JScrollPane scrollpane =</p>

  <p>JTable.createScrollPaneForTable(table);</p>

  <p>add(scrollpane, BorderLayout.CENTER);</p>

  <p>}</p>

  <p>public static void main(String args[]) {</p>

  <p>Show.inFrame(new Table(),200,200);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>DateModel包括一组数据，但我们同样能从其它的地方得到数据，例如从数据库中。构建器增加了一个TableModelListener用来在每次表格被改变后打印数组。剩下的方法都遵循Bean的命名规则，并且当JTable需要在DateModel中显示信息时调用。AbstractTableModel提供了默认的setValueAt()和isCellEditable()方法以防止修改这些数据，因此如果我们想修改这些数据，就必须过载这些方法。</p>

  <p>一旦我们拥有一个TableModel，我们只需要将它分配给JTable构建器即可。所有有关显示，编辑和更新的详细资料将为我们处理。注意这个程序例子同样将JTable放置在JScrollPane中，这是因为JScrollPane需要一个特殊的JTable方法。</p>

  <p>13.19.15 卡片式对话框</p>

  <p>在本章的前部，向我们介绍了老式的CardLayout，并且注意到我们怎样去管理我们所有的卡片开关。有趣的是，有人现在认为这是一种不错的设计。幸运的是，Swing用JTabbedPane对它进行了修补，由JTabbedPane来处理这些卡片，开关和其它的任何事物。对比CardLayout和JTabbedPane，我们会发现惊人的差异。</p>

  <p>下面的程序例子十分的有趣，因为它利用了前面例子的设计。它们都是做为JPanel的衍生物来构建的，因此这个程序将安放前面的每个例子到它自己在JTabbedPane的窗格中。我们会看到利用RTTI制造的程序十分的小巧精致：</p>

  <p>//: Tabbed.java</p>

  <p>// Using tabbed panes</p>

  <p>package c13.swing;</p>

  <p>import java.awt.*;</p>

  <p>import javax.swing.*;</p>

  <p>import javax.swing.border.*;</p>

  <p>public class Tabbed extends JPanel {</p>

  <p>static Object[][] q = {</p>

  <p>{ "Felix", Borders.class },</p>

  <p>{ "The Professor", Buttons.class },</p>

  <p>{ "Rock Bottom", ButtonGroups.class },</p>

  <p>{ "Theodore", Faces.class },</p>

  <p>{ "Simon", Menus.class },</p>

  <p>{ "Alvin", Popup.class },</p>

  <p>{ "Tom", ListCombo.class },</p>

  <p>{ "Jerry", Progress.class },</p>

  <p>{ "Bugs", Trees.class },</p>

  <p>{ "Daffy", Table.class },</p>

  <p>};</p>

  <p>static JPanel makePanel(Class c) {</p>

  <p>String title = c.getName();</p>

  <p>title = title.substring(</p>

  <p>title.lastIndexOf('.') + 1);</p>

  <p>JPanel sp = null;</p>

  <p>try {</p>

  <p>sp = (JPanel)c.newInstance();</p>

  <p>} catch(Exception e) {</p>

  <p>System.out.println(e);</p>

  <p>}</p>

  <p>sp.setBorder(new TitledBorder(title));</p>

  <p>return sp;</p>

  <p>}</p>

  <p>public Tabbed() {</p>

  <p>setLayout(new BorderLayout());</p>

  <p>JTabbedPane tabbed = new JTabbedPane();</p>

  <p>for(int i = 0; i &lt; q.length; i++)</p>

  <p>tabbed.addTab((String)q[i][0],</p>

  <p>makePanel((Class)q[i][1]));</p>

  <p>add(tabbed, BorderLayout.CENTER);</p>

  <p>tabbed.setSelectedIndex(q.length/2);</p>

  <p>}</p>

  <p>public static void main(String args[]) {</p>

  <p>Show.inFrame(new Tabbed(),460,350);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>再者，我们可以注意到使用的数组构造式样：第一个元素是被置放在卡片上的String，第二个元素是将被显示在对应窗格上JPanel类。在Tabbed()构建器里，我们可以看到两个重要的JTabbedPane方法被使用：addTab()插入一个新的窗格，setSelectedIndex()选择一个窗格并从它开始。（一个在中间被选中的窗格证明我们不必从第一个窗格开始）。</p>

  <p>当我们调用addTab()方法时，我们为它提供卡片的String和一些组件（也就是说，一个AWT组件，而不是一个来自AWT的JComponent）。这个组件会被显示在窗格中。一旦我们这样做了，自然而然的就不需要更多管理了――JTabbedPane会为我们处理其它的任何事。</p>

  <p>makePanel()方法获取我们想创建的类Class对象和用newInstance()去创建并造型为JPanel（当然，假定那些类是必须从JPanel继承才能增加的类，除非在这一节中为程序例子的结构所使用）。它增加了一个包括类名并返回结果的TitledBorder，以作为一个JPanel在addTab()被使用。</p>

  <p>当我们运行程序时，我们会发现如果卡片太多，填满了一行，JTabbedPane自动地将它们堆积起来。</p>

  <p>13.19.16 Swing消息框</p>

  <p>开窗的环境通常包含一个标准的信息框集，允许我们很快传递消息给用户或者从用户那里捕捉消息。在Swing里，这些信息窗被包含在JOptionPane里的。我们有一些不同的可能实现的事件（有一些十分复杂），但有一点，我们必须尽可能的利用static JOptionPane.showMessageDialog()和 JOptionPane.showConfirmDialog()方法，调用消息对话框和确认对话框。</p>

  <p>13.19.17 Swing更多的知识</p>

  <p>这一节意味着唯一向我们介绍的是Swing的强大力量和我们的着手处，因此我们能注意到通过库，我们会感觉到我们的方法何等的简单。到目前为止，我们已看到的可能足够满足我们UI设计需要的一部分。不过，这里有许多有关Swing额外的情况――它有意成为一全功能的UI设计工具箱。如果我们没有发现我们所需要的，请到SUN公司的在线文件中去查找，并搜索WEB。这个方法几乎可以完成我们能想到的任何事。</p>

  <p>本节中没有涉及的一些要点：</p>

  <p>■更多特殊的组件，例如JColorChooser,JFileChooser,JPasswordField,JHTMLPane（完成简单的HTML格式化和显示）以及JTextPane（一个支持格式化，字处理和图像的文字编辑器）。它们都非常易用。</p>

  <p>■Swing的新的事件类型。在一些方法中，它们看起来像违例：类型非常的重要，名字可以被用来表示除了它们自己之外的任何事物。</p>

  <p>■新的布局管理：Springs &amp; Struts以及BoxLayout</p>

  <p>■分裂控制：一个间隔物式的分裂条，允许我们动态地处理其它组件的位置。</p>

  <p>■JLayeredPane和JInternalFrame被一起用来在当前帧中创建子帧，以产生多文件接口（MDI）应用程序。</p>

  <p>■可插入的外观和效果，因此我们可以编写单个的程序可以像期望的那样动态地适合不同的平台和操作系统。</p>

  <p>■自定义光标。</p>

  <p>■JToolbar API提供的可拖动的浮动工具条。</p>

  <p>■双缓存和为平整屏幕重新画线的自动重画批次。</p>

  <p>■内建“取消”支持。</p>

  <p>■拖放支持。</p>

  <p>13.20 总结</p>

  <p>对于AWT而言，Java 1.1到Java 1.2最大的改变就是Java中所有的库。Java 1.0版的AWT曾作为目前见过的最糟糕的一个设计被彻底地批评，并且当它允许我们在创建小巧精致的程序时，产生的GUI“在所有的平台上都同样的平庸”。它与在特殊平台上本地应用程序开发工具相比也是受到限制的，笨拙的并且也是不友好的。当Java 1.1版纳入新的事件模型和Java Beans时，平台被设置――现在它可以被拖放到可视化的应用程序构建工具中，创建GUI组件。另外，事件模型的设计和Bean无疑对轻松的编程和可维护的代码都非常的在意（这些在Java 1.0 AWT中不那么的明显）。但直至GUI组件－JFC/Swing类－显示工作结束它才这样。对于Swing组件而言，交叉平台GUI编程可以变成一种有教育意义的经验。</p>

  <p>现在，唯一的情况是缺乏应用程序构建工具，并且这就是真正的变革的存在之处。微软的Visual Basic和Visual C++需要它们的应用程序构建工具，同样的是Borland的Delphi和C++构建器。如果我们需要应用程序构建工具变得更好，我们不得不交叉我们的指针并且希望自动授权机会给我们所需要的。Java是一个开放的环境，因此不但考虑到同其它的应用程序构建环境竞争，而且Java还促进它们的发展。这些工具被认真地使用，它们必须支持Java Beans。这意味着一个平等的应用领域：如果一个更好的应用程序构建工具出现，我们不需要去约束它就可以使用――我们可以采用并移动到新的工具上工作即可，这会提高我们的工作效率。这种竞争的环境对应用程序构建工具来说从未出现过，这种竞争能真正提高程序设计者的工作效率。</p>

  <p>13.21 练习</p>

  <p>(1)创建一个有文字字段和三个按钮的程序片。当我们按下每个按钮时，使不同的文字显示在文字段中。</p>

  <p>(2)增加一个复选框到练习1创建的程序中，捕捉事件，并插入不同的文字到文字字段中。</p>

  <p>(3)创建一个程序片并增加所有导致action()被调用的组件，然后捕捉他们的事件并在文字字段中为每个组件显示一个特定的消息。</p>

  <p>(4)增加可以被handleEvent()方法测试事件的组件到练习3中。过载handleEvent()并在文字字段中为每个组件显示特定的消息。</p>

  <p>(5)创建一个有一个按钮和一个TextField的程序片。编写一个handleEvent()，以便如果按钮有焦点，输入字符到将显示的TextField中。</p>

  <p>(6)创建一个应用程序并将本章所有的组件增加主要的帧，包括菜单和对话框。</p>

  <p>(7)修改TextNew.java，以便字母在t2中保持输入时的样子，取代自动变成大写。</p>

  <p>(8)修改CardLayout1.java以便它使用Java 1.1的事件模型。</p>

  <p>(9)增加Frog.class到本章出现的清单文件中并运行jar以创建一个包括Frog和BangBean的JAR文件。现在从SUN公司处下载并安装BDK或者使用我们自己的可激活Bean的程序构建工具并增加JAR文件到我们的环境中，因此我们可以测试两个Bean。</p>

  <p>(10)创建我们自己的包括两个属性：一个布尔值为“on”，另一个为整型“level”，称为Valve的Java Bean。创建一个清单文件，利用jar打包我们的Bean，然后读入它到beanbox或到我们自己的激活程序构建工具里，因此我们可以测试它。</p>

  <p>(11)修改Menus.java，以便它处理多级菜单。这要假设读者已经熟悉了HTML的基础知识。但那些东西并不难理解，而且有一些书和资料可供参考。英文版主页 | 中文版主页 | 详细目录 | 关于译者</p>

  <p>-------------------------------------------------</p>

  <p>TXT书库 www.16txt.com</p>

  <p>-------------------------------------------------第14章 多线程</p>

  <p>利用对象，可将一个程序分割成相互独立的区域。我们通常也需要将一个程序转换成多个独立运行的子任务。</p>

  <p>象这样的每个子任务都叫作一个“线程”（Thread）。编写程序时，可将每个线程都想象成独立运行，而且都有自己的专用CPU。一些基础机制实际会为我们自动分割CPU的时间。我们通常不必关心这些细节问题，所以多线程的代码编写是相当简便的。</p>

  <p>这时理解一些定义对以后的学习狠有帮助。“进程”是指一种“自包容”的运行程序，有自己的地址空间。“多任务”操作系统能同时运行多个进程（程序）――但实际是由于CPU分时机制的作用，使每个进程都能循环获得自己的CPU时间片。但由于轮换速度非常快，使得所有程序好象是在“同时”运行一样。“线程”是进程内部单一的一个顺序控制流。因此，一个进程可能容纳了多个同时执行的线程。</p>

  <p>多线程的应用范围很广。但在一般情况下，程序的一些部分同特定的事件或资源联系在一起，同时又不想为它而暂停程序其他部分的执行。这样一来，就可考虑创建一个线程，令其与那个事件或资源关联到一起，并让它独立于主程序运行。一个很好的例子便是“Quit”或“退出”按钮――我们并不希望在程序的每一部分代码中都轮询这个按钮，同时又希望该按钮能及时地作出响应（使程序看起来似乎经常都在轮询它）。事实上，多线程最主要的一个用途就是构建一个“反应灵敏”的用户界面。</p>

  <p>14.1 反应灵敏的用户界面</p>

  <p>作为我们的起点，请思考一个需要执行某些CPU密集型计算的程序。由于CPU“全心全意”为那些计算服务，所以对用户的输入十分迟钝，几乎没有什么反应。在这里，我们用一个合成的applet/application（程序片／应用程序）来简单显示出一个计数器的结果：</p>

  <p>//: Counter1.java</p>

  <p>// A non-responsive user interface</p>

  <p>package c14;</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import java.applet.*;</p>

  <p>public class Counter1 extends Applet {</p>

  <p>private int count = 0;</p>

  <p>private Button</p>

  <p>onOff = new Button("Toggle"),</p>

  <p>start = new Button("Start");</p>

  <p>private TextField t = new TextField(10);</p>

  <p>private boolean runFlag = true;</p>

  <p>public void init() {</p>

  <p>add(t);</p>

  <p>start.addActionListener(new StartL());</p>

  <p>add(start);</p>

  <p>onOff.addActionListener(new OnOffL());</p>

  <p>add(onOff);</p>

  <p>}</p>

  <p>public void go() {</p>

  <p>while (true) {</p>

  <p>try {</p>

  <p>Thread.currentThread().sleep(100);</p>

  <p>} catch (InterruptedException e){}</p>

  <p>if(runFlag)</p>

  <p>t.setText(Integer.toString(count++));</p>

  <p>}</p>

  <p>}</p>

  <p>class StartL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>go();</p>

  <p>}</p>

  <p>}</p>

  <p>class OnOffL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>runFlag = !runFlag;</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Counter1 applet = new Counter1();</p>

  <p>Frame aFrame = new Frame("Counter1");</p>

  <p>aFrame.addWindowListener(</p>

  <p>new WindowAdapter() {</p>

  <p>public void windowClosing(WindowEvent e) {</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>});</p>

  <p>aFrame.add(applet, BorderLayout.CENTER);</p>

  <p>aFrame.setSize(300,200);</p>

  <p>applet.init();</p>

  <p>applet.start();</p>

  <p>aFrame.setVisible(true);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>在这个程序中，AWT和程序片代码都应是大家熟悉的，第13章对此已有很详细的交待。go()方法正是程序全心全意服务的对待：将当前的count（计数）值置入TextField（文本字段）t，然后使count增值。</p>

  <p>go()内的部分无限循环是调用sleep()。sleep()必须同一个Thread（线程）对象关联到一起，而且似乎每个应用程序都有部分线程同它关联（事实上，Java本身就是建立在线程基础上的，肯定有一些线程会伴随我们写的应用一起运行）。所以无论我们是否明确使用了线程，都可利用Thread.currentThread()产生由程序使用的当前线程，然后为那个线程调用sleep()。注意，Thread.currentThread()是Thread类的一个静态方法。</p>

  <p>注意sleep()可能“掷”出一个InterruptException（中断违例）――尽管产生这样的违例被认为是中止线程的一种“恶意”手段，而且应该尽可能地杜绝这一做法。再次提醒大家，违例是为异常情况而产生的，而不是为了正常的控制流。在这里包含了对一个“睡眠”线程的中断，以支持未来的一种语言特性。</p>

  <p>一旦按下start按钮，就会调用go()。研究一下go()，你可能会很自然地（就象我一样）认为它该支持多线程，因为它会进入“睡眠”状态。也就是说，尽管方法本身“睡着”了，CPU仍然应该忙于监视</p>

  <div class="mbppagebreak"></div>
</body>
</html>
